中文

了解六边形架构(又称端口与适配器)如何提高应用程序的可维护性、可测试性和灵活性。本指南为全球开发者提供实用的示例和可行的见解。

六边形架构:端口与适配器的实践指南

在不断发展的软件开发领域,构建健壮、可维护和可测试的应用程序至关重要。六边形架构,也称为端口与适配器,是一种架构模式,通过将应用程序的核心业务逻辑与其外部依赖解耦来解决这些问题。本指南旨在为全球开发者提供对六边形架构、其优势以及实际实现策略的全面理解。

什么是六边形架构?

六边形架构由 Alistair Cockburn 提出,其核心思想是将应用程序的核心业务逻辑与其外部世界隔离开来。这种隔离通过使用端口适配器来实现。

可以这样理解:核心应用程序位于中心,被六边形外壳包围。端口是该外壳的入口和出口,适配器插入这些端口,将核心连接到外部世界。

六边形架构的关键原则

几个关键原则支撑着六边形架构的有效性:

使用六边形架构的好处

采用六边形架构提供了许多优势:

实施六边形架构:实践示例

让我们通过一个简化的用户注册系统示例来说明六边形架构的实现。我们将使用一种假设的编程语言(类似于 Java 或 C#)来说明。

1. 定义核心(应用程序)

核心应用程序包含注册新用户的业务逻辑。


// Core/UserService.java (或 UserService.cs)
public class UserService {
    private final UserRepository userRepository;
    private final PasswordHasher passwordHasher;
    private final UserValidator userValidator;

    public UserService(UserRepository userRepository, PasswordHasher passwordHasher, UserValidator userValidator) {
        this.userRepository = userRepository;
        this.passwordHasher = passwordHasher;
        this.userValidator = userValidator;
    }

    public Result<User, String> registerUser(String username, String password, String email) {
        // Validate user input
        ValidationResult validationResult = userValidator.validate(username, password, email);
        if (!validationResult.isValid()) {
            return Result.failure(validationResult.getErrorMessage());
        }

        // Check if user already exists
        if (userRepository.findByUsername(username).isPresent()) {
            return Result.failure("Username already exists");
        }

        // Hash the password
        String hashedPassword = passwordHasher.hash(password);

        // Create a new user
        User user = new User(username, hashedPassword, email);

        // Save the user to the repository
        userRepository.save(user);

        return Result.success(user);
    }
}

2. 定义端口

我们定义核心应用程序用于与外部世界交互的端口。


// Ports/UserRepository.java (或 UserRepository.cs)
public interface UserRepository {
    Optional<User> findByUsername(String username);
    void save(User user);
}

// Ports/PasswordHasher.java (或 PasswordHasher.cs)
public interface PasswordHasher {
    String hash(String password);
}

//Ports/UserValidator.java (或 UserValidator.cs)
public interface UserValidator{
  ValidationResult validate(String username, String password, String email);
}

//Ports/ValidationResult.java (或 ValidationResult.cs)
public interface ValidationResult{
  boolean isValid();
  String getErrorMessage();
}

3. 定义适配器

我们实现将核心应用程序连接到特定技术的适配器。


// Adapters/DatabaseUserRepository.java (或 DatabaseUserRepository.cs)
public class DatabaseUserRepository implements UserRepository {
    private final DatabaseConnection databaseConnection;

    public DatabaseUserRepository(DatabaseConnection databaseConnection) {
        this.databaseConnection = databaseConnection;
    }

    @Override
    public Optional<User> findByUsername(String username) {
        // Implementation using JDBC, JPA, or another database access technology
        // ...
        return Optional.empty(); // Placeholder
    }

    @Override
    public void save(User user) {
        // Implementation using JDBC, JPA, or another database access technology
        // ...
    }
}

// Adapters/BCryptPasswordHasher.java (或 BCryptPasswordHasher.cs)
public class BCryptPasswordHasher implements PasswordHasher {
    @Override
    public String hash(String password) {
        // Implementation using BCrypt library
        // ...
        return "hashedPassword"; //Placeholder
    }
}

//Adapters/SimpleUserValidator.java (或 SimpleUserValidator.cs)
public class SimpleUserValidator implements UserValidator {
  @Override
  public ValidationResult validate(String username, String password, String email){
    //Simple Validation logic
     if (username == null || username.isEmpty()) {
            return new SimpleValidationResult(false, "Username cannot be empty");
        }
        if (password == null || password.length() < 8) {
            return new SimpleValidationResult(false, "Password must be at least 8 characters long");
        }
        if (email == null || !email.contains("@")) {
            return new SimpleValidationResult(false, "Invalid email format");
        }

        return new SimpleValidationResult(true, null);
  }
}

//Adapters/SimpleValidationResult.java (或 SimpleValidationResult.cs)
public class SimpleValidationResult implements ValidationResult {
  private final boolean valid;
  private final String errorMessage;

    public SimpleValidationResult(boolean valid, String errorMessage) {
        this.valid = valid;
        this.errorMessage = errorMessage;
    }
  @Override
  public boolean isValid(){
    return valid;
  }

  @Override
  public String getErrorMessage(){
    return errorMessage;
  }
}



//Adapters/WebUserController.java (或 WebUserController.cs)
//Driving Adapter - handles requests from the web
public class WebUserController {
    private final UserService userService;

    public WebUserController(UserService userService) {
        this.userService = userService;
    }

    public String registerUser(String username, String password, String email) {
        Result<User, String> result = userService.registerUser(username, password, email);
        if (result.isSuccess()) {
            return "Registration successful!";
        } else {
            return "Registration failed: " + result.getFailure();
        }
    }
}

4. 组合

将所有内容连接起来。请注意,此组合(依赖注入)通常发生在应用程序的入口点或依赖注入容器中。


//Main class or dependency injection configuration
public class Main {
    public static void main(String[] args) {
        // Create instances of the adapters
        DatabaseConnection databaseConnection = new DatabaseConnection("jdbc:mydb://localhost:5432/users", "user", "password");
        DatabaseUserRepository userRepository = new DatabaseUserRepository(databaseConnection);
        BCryptPasswordHasher passwordHasher = new BCryptPasswordHasher();
        SimpleUserValidator userValidator = new SimpleUserValidator();

        // Create an instance of the core application, injecting the adapters
        UserService userService = new UserService(userRepository, passwordHasher, userValidator);

        //Create a driving adapter and connect it to the service
        WebUserController userController = new WebUserController(userService);

        //Now you can handle user registration requests through the userController
        String result = userController.registerUser("john.doe", "P@sswOrd123", "john.doe@example.com");
        System.out.println(result);
    }
}



//DatabaseConnection is a simple class for demonstration purposes only
class DatabaseConnection {
    private String url;
    private String username;
    private String password;

    public DatabaseConnection(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    // ... methods to connect to the database (not implemented for brevity)
}

//Result class (similar to Either in functional programming)
class Result<T, E> {
    private final T success;
    private final E failure;
    private final boolean isSuccess;

    private Result(T success, E failure, boolean isSuccess) {
        this.success = success;
        this.failure = failure;
        this.isSuccess = isSuccess;
    }

    public static <T, E> Result<T, E> success(T value) {
        return new Result<>(value, null, true);
    }

    public static <T, E> Result<T, E> failure(E error) {
        return new Result<>(null, error, false);
    }

    public boolean isSuccess() {
        return isSuccess;
    }

    public T getSuccess() {
        if (!isSuccess) {
            throw new IllegalStateException("Result is a failure");
        }
        return success;
    }

    public E getFailure() {
        if (isSuccess) {
            throw new IllegalStateException("Result is a success");
        }
        return failure;
    }
}

class User {
    private String username;
    private String password;
    private String email;

    public User(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    // getters and setters (omitted for brevity)

}

Explanation:

高级注意事项和最佳实践

虽然六边形架构的基本原理很简单,但有一些高级注意事项需要牢记:

实际应用中的六边形架构示例

许多成功的公司和项目都采用了六边形架构来构建健壮且可维护的系统:

挑战和权衡

虽然六边形架构提供了显著的好处,但重要的是要认识到所涉及的挑战和权衡:

仔细评估六边形架构在您的特定项目需求和团队能力下的好处和挑战至关重要。它不是万能的,它可能并非适用于所有项目。

结论

六边形架构通过其对端口和适配器的强调,提供了一种构建可维护、可测试和灵活应用程序的强大方法。通过将核心业务逻辑与外部依赖解耦,它可以让您轻松适应不断变化的技术和需求。虽然存在挑战和权衡需要考虑,但六边形架构的优势通常会超过成本,特别是对于复杂且长期的应用程序。通过拥抱依赖倒置和显式接口的原则,您可以创建更具弹性、更易于理解并且能更好地满足现代软件格局需求的系统。

本指南提供了六边形架构的全面概述,从其核心原则到实际实现策略。我们鼓励您进一步探索这些概念,并在您自己的项目中尝试应用它们。学习和采用六边形架构的投资无疑将在长期内获得回报,带来更高质量的软件和更满意的开发团队。

最终,选择正确的架构取决于您项目的具体需求。在做出决定时,请考虑复杂性、使用寿命和可维护性要求。六边形架构为构建健壮且适应性强的应用程序提供了坚实的基础,但它只是软件架构师工具箱中的一个工具。